// ###########################################################################################################################
// Establishes the basic setup and integration of the DreamSpace module.
// ###########################################################################################################################

module DreamSpace.System {

    // =======================================================================================================================

    /** Represents an isolated environment where an application can execute.
    * An application domain encapsulates application resources into one isolated object space.
    * For example, in case of network objects, each object is assigned an ID for tracking.  These IDs are stored in
    * the domain for fast lookup.  Too many applications may create too many objects in one place, or errors in one
    * application may mess up objects in another (i.e. invalid IDs in network updates).  Using domains can keep
    * applications from conflicting with one another.
    * Note: It is ok to have multiple applications for one domain instance; however, each domain instance can only have 
    * one DEFAULT application reference.
    */
    export class AppDomain extends Object.$Type implements IDisposable {
        /** The default system wide application domain.
        * Never change this value directly.
        * See 'System.Platform.AppDomain' for more details.
        */
        static defaultDomain = new AppDomain();

        /** A list of all application domains in the system.  */
        static appDomains: AppDomain[] = [AppDomain.defaultDomain];

        /** Holds all the class types in the system registered by calling 'AppDomain.registerType()'. */
        static types: { [fullTypeName: string]: ITypeInfo } = {};

        /** A type bridge is a object instance who's prototype points to the actual static function object.
        * Each time "{AppDomain}.with()" is used, a bridge is created and cached here under the type name for reuse.
        */
        private __typeBridges: { [fullTypeName: string]: IADBridge } = {};

        /** A collection of all objects created via this application domain instance. Each object is given an ID value
        * that is unique for this specific AppDomain instance only.
        */
        objects: Collections.IndexedObjectCollection<{ __id: number }> = new Collections.IndexedObjectCollection<{ __id: number }>();

        private static __disposedObjects: { [fulltypename: string]: { __id: number }[]; } = {}; // (can be reused by any AppDomain instance! [global pool for better efficiency])

        /** Returns the default (first) application for this domain. */
        application(): Platform.Application.$Type {
            if (!this.applications.length)
                throw Exception.error("AppDomain", "This application domain has no application instances.", this);
            return this.applications[0];
        }

        /** Holds a list of all applications on this domain.
        * Do not add applications directly to this array.  Use the 'createApplication()' function.
        * To remove an application, just dispose it.
        */
        applications: Platform.Application.$Type[];

        // -------------------------------------------------------------------------------------------------------------------

        /** Constructs an application domain for the specific application instance. */
        constructor(application?: Platform.Application.$Type) {
            super();
            this.__appDomain = this;
            this.applications = [application];
            if (Object.freeze)
                Object.freeze(this); // (properties cannot be modified once set)
        }

        /** Disposes this AppDomain instance. */
        dispose(): void;
        /** Disposes a specific object in this AppDomain instance. 
        * When creating thousands of objects continually, object disposal (and subsequent cache of the instances) means the GC doesn't have
        * to keep engaging to clear up the abandoned objects.  While using the "new" operator may be faster than using "{type}.new()" at
        * first, the application actually becomes very lagged while the GC keeps eventually kicking in.  This is why DreamSpace objects are
        * cached and reused as much as possible.
        * @param {object} object The object to dispose and release back into the "disposed objects" pool.
        * @param {boolean} release If true (default) allows the objects to be released back into the object pool.  Set this to
        *                          false to request that child objects remain connected after disposal (not released). This
        *                          can allow quick initialization of a group of objects, instead of having to pull each one
        *                          from the object pool each time.
        */
        dispose(object: IAppDomainDisposable, release?: boolean): void;
        dispose(object?: IAppDomainDisposable, release: boolean = true): void {
            if (object !== void 0) {
                // ... make sure 'dispose()' was called on the object ...

                if (object.dispose != noop) { // ('object.dispose' is set to 'noop' if '{AppDomain}.dispose()' is called via 'object.dispose()')
                    object.__appDomain = void 0; // (need to make sure this '{AppDomain}.dispose()' function doesn't get called again)
                    dispose(object, release); // (object will not call back due to above)
                }

                // ... remove the object from the "active" list and erase it ...

                this.objects.removeObject(object);
                Utilities.erase(this, release); // ('__id' will be erased, and serve to flag 'disposal completed'; note: any active/undisposed object)

                if (!release)
                    object.__appDomain = this;
                else {
                    // ... place the object into the disposed objects list ...

                    var type: ITypeInfo = <any>object.constructor;
                    if (!type.$__fullname)
                        throw Exception.error("dispose()", "The object type is not registered.  Please see one of the AppDomain 'registerClass()/registerType()' functions for more details.");
                    var funcList = AppDomain.__disposedObjects[type.$__fullname];
                    if (!funcList)
                        AppDomain.__disposedObjects[type.$__fullname] = funcList = [];
                    funcList.push(object);
                }
            } else {
                for (var i = this.applications.length - 1; i >= 0; --i)
                    dispose(this.applications[i]);
                this.applications.length = 0;
            }
        }

        ///** Registers a given type by name (constructor function), and creates the function on the last specified module if it doesn't already exist.
        //* @param {Function} type The type (constructor function) to register.
        //* @param {modules} parentModules A list of all modules up to the current type, usually starting with 'DreamSpace' as the first module.
        //* @param {boolean} addMemberTypeInfo If true (default), all member functions of any existing type (function object) will have type information
        //* applied (using the IFunctionInfo interface).
        //*/
        //static registerType<T extends (...args)=>any>(type: string, parentModules: {}[], addMemberTypeInfo?: boolean): T;
        //static registerType(type: Function, parentModules: {}[], addMemberTypeInfo?: boolean): ITypeInfo;

        /** Registers a given type (constructor function).  The type reference is expected to already exist.
        * To create a new type, call the string overloaded version.
        * @param {Function} type The type (constructor function) to register.
        * @param {modules} parentModules A list of all modules up to the current type, usually starting with 'DreamSpace' as the first module.
        * @param {boolean} addMemberTypeInfo If true (default), all member functions on the type (function object) will have type information
        * applied (using the IFunctionInfo interface).
        */
        static registerType(type: Function, parentModules: {}[], addMemberTypeInfo: boolean = true): any {

            if (!parentModules) parentModules = [];

            if (typeof type !== 'function') {
                // ... create the constructor function if missing ...
                var topMostModule = parentModules[parentModules.length - 1] || DreamSpace.global;
                var typeName = "" + type;
                var classMod = <Function>topMostModule[typeName];
                if (typeof classMod !== 'function' && typeof classMod !== 'object') {
                    // ... make a constructor bridge, as a function closure, on the current type information for obtaining new/disposed instances ...
                    classMod = function () {
                        // (note: this function may be called with an empty object context [of the expected type] and only one '__appDomain' property, in which '$__shellType' will be missing)
                        var bridge = <IADBridge>this; // (note: this should be either a bridge, or a class module object, or undefined)
                        if (this !== void 0 && Object.isEmpty(this))
                            throw Exception.error("{Type}()", "Using the 'new' operator on a class module (or supplying external instances as context) is not supported in this system.", classMod);
                        var appDomain = bridge.__appDomain;
                        var shellType = bridge.$__shellType;
                        var instance = null;
                        if (!shellType) {
                            bridge = (appDomain || AppDomain.defaultDomain).with(<any>classMod);
                            shellType = bridge.$__shellType;
                            if (bridge != this && bridge.constructor == classMod)
                                instance = this;
                        }
                        // ... get instance from the pool (of the same type!), or create a new one ...
                        if (!instance) {
                            var fullTypeName = (<ITypeInfo><any>bridge.constructor).$__fullname;
                            var objectPool = fullTypeName && AppDomain.__disposedObjects[fullTypeName];
                            instance = objectPool && objectPool.length ? objectPool.pop() : new shellType();
                        }
                        Delegate.fastApply(bridge.constructor, instance, arguments);
                        return instance;
                    };

                    topMostModule[typeName] = type = classMod; /*WARNING: May break on minification:*///eval("(function(classMod){return (" + classMod.toString().replace("function ", "function " + typeName) + ");})")(classMod); // (typeName added, since TS includes this on constructor functions)
                }
                else if ((<ITypeInfo><any>classMod).$__name)
                    throw Exception.error("registerType()", "The type '" + (<ITypeInfo><any>classMod).$__fullname + "' has already been registered.", classMod);
                else
                    type = classMod;
            }

            var _type: ITypeInfo = type, namespace: {} = DreamSpace.global, mod: IModuleInfo, pname: string, name: string, fullname: string;

            parentModules[parentModules.length++] = type; // (put the type on the end so we can include this in the loop also)

            if (!_type.$__parent || !_type.$__fullname) {
                for (var i = 0, n = parentModules.length; i < n; ++i) {
                    mod = <IModuleInfo>parentModules[i];
                    if (!mod.$__parent) {
                        name = "";
                        for (pname in namespace)
                            if (namespace[pname] === mod) {
                                name = pname;
                                break;
                            }
                        if (!name)
                            throw Exception.error("registerType()", ((i == n - 1) ? "The type to be registered" : (i == 0 ? "The first module" : "Module " + (1 + i))) + " cannot be found in the" + ((i == 0) ? " root scope." : " preceding module.")
                                + " Please double check you have the correct modules that precede the type being registered.", _type);
                        fullname = fullname ? fullname + "." + name : name;
                        mod.$__parent = <IModuleInfo>namespace; // (each module will have a reference to its parent module [object], its local and full type names; note: the DreamSpace parent will be pointing to 'DreamSpace.global')
                        mod.$__modules = <any>mod;
                        mod.$__name = name;
                        mod.$__fullname = fullname;
                    }
                    namespace = mod;
                }

                _type.$__fullname = fullname = fullname ? fullname + "." + _type.$__name : _type.$__name;
            }
            else fullname = _type.$__fullname;

            // ... scan the type's prototype functions and update the type information (only function names at this time) ...
            // TODO: Consider parsing the function parameters as well and add this information for developers. (but remember 'type' may be a module before it becomes a function)

            if (addMemberTypeInfo) {
                var prototype = type['prototype'], func: IFunctionInfo;

                for (var pname in prototype) {
                    func = prototype[pname];
                    if (typeof func == 'function') {
                        func.$__parent = <IModuleInfo>_type;
                        func.$__name = pname;
                        func.$__fullname = fullname + "." + pname;
                    }
                }
            }

            // ... register the type ...
            // (all registered type names will be made available here globally, since types are not AppDomain specific)

            AppDomain.types[fullname] = <ITypeInfo><any>type;

            return _type;
        }

        /** Registers a class type and returns a 'new' function reference for instantiating new objects from the registered type.
        * Note: Prototype functions for registered types are also updated to support the ITypeInfo interface.
        * @param {object} classModule The class module container to register (the class to register in the module must have the name '$Type').
        * @param {modules} parentModules A list of all modules up to the current type, usually starting with 'DreamSpace' as the first module.
        * Tip: If intellisense is available, hover your mouse cursor over the class type to see the full namespace path.
        * @param {boolean} addMemberTypeInfo If true (default), all class member functions on the type (function object) will have type information
        * applied (using the IFunctionInfo interface).
        */
        static registerClass<TConstructor extends IClassModule<Object>>(classModule: TConstructor /*{ getTypeName(): string; $__parent: ITypeInfo; $__name: string; $__fullname: string; }*/,
            parentModules: {}[], addMemberTypeInfo: boolean = true): TConstructor {
            // ... get the type name for the class (only available on classes; modules end up being simple objects only) ...
            // (note: if 'classType' will be replaced on the last module with a new constructor function)

            if (typeof classModule !== 'function')
                throw Exception.error("registerClass()", "The 'type' argument is not a valid constructor function.", classModule);

            //??var className = "$Type"; //??'T' + modInfo.$__name;
            var classType: ITypeInfo = <any>classModule.$Type;
            if (classType == void 0)
                throw Exception.error("registerClass()", "Class '$Type' could not be found in module '" + modInfo.$__name + "'.", classModule);
            if (typeof classType !== 'function' || !(classType instanceof Object))
                throw Exception.error("registerClass()", "Class '$Type' in module '" + modInfo.$__name + "' is not of type 'System.Object'.", classModule);

            // ... validate that type info exists, otherwise add/update it ...

            var modInfo = <IModuleInfo><any>classModule;
            if (!modInfo.$__name || !modInfo.$__fullname || !modInfo.$__parent)
                this.registerType(<Function><any>classType, parentModules, addMemberTypeInfo);
            //throw Exception.error("registerClass()", "The 'classModule' argument does not contain any valid classes to register.", classModule);

            var classMod = classModule;

            // ... establish the static property definitions ...
            Platform.Property.__completePropertyRegistration(classModule);

            // ... make the bridge constructor function of the same type (same proto chain) of class type ...

            classMod.prototype = (<Function><any>classType).prototype;

            // ... update type information ...

            classType.$__parent = modInfo;
            classType.$__name = "$Type";
            classType.$__fullname = modInfo.$__fullname + ".$Type";

            var parentModules = [];
            var mod = modInfo;
            while (mod && <{}>mod != <{}>DreamSpace.global) { parentModules.unshift(mod); mod = mod.$__parent; }

            for (var pname in classModule)
                if ((<NativeTypes.IObject>classModule).hasOwnProperty(pname))
                    if (classModule[name] instanceof Object)
                        AppDomain.registerType(classModule[name], parentModules);

            // ... copy any static properties on the type to the class module ...

            for (var pname in classType)
                if ((<NativeTypes.IObject>classType).hasOwnProperty(pname) && classModule[pname] === void 0)
                    classModule[pname] = classType[pname];

            classMod.toString = function (): string { return (<IModuleInfo>this).$__fullname; }
                classMod.valueOf = classMod.toString;

            return classMod;
        }

        /** Attaches the object to this AppDomain.  The object must never exist in any other domains prior to this call.
        * 'ITypeInfo' details will be updated for the object.  This is useful when 
        * See also: {AppDomain}.registerType()
        * @param {T} object The object to add to this app domain.
        * @param {{}} parentModule The parent module or scope in which the type exists.
        * @param {{}} parentModule The parent module or scope in which the type exists.
        */
        attachObject<T extends {}>(object: T): T {
            if (!type.$__parent || !type.$__name || !type.$__fullname)
                throw Exception.error("with()", "The specified type '" + type.$__name + "' has not yet been registered properly using 'AppDomain.registerType()/.registerClass()'.", type);
            var type: IFunctionInfo = <IFunctionInfo>object.constructor;
            this.objects.addObject(<any>object);
            return object;
        }

        /** Selects this application domain instance as the active domain when calling methods on a type. This is usually used
        * when calling the '{type}.new()' method for creating new instances.
        * Note: The focus for this AppDomain instance is limited to this call only (i.e. not persisted).
        */
        with<TClassModule extends IClassModule<Object.$Type>>(classModule: TClassModule): TClassModule {
            var typeInfo = <ITypeInfo><any>classModule;
            if (!typeInfo.$__parent || !typeInfo.$__name || !typeInfo.$__fullname)
                throw Exception.error("with()", "The specified class module has not yet been registered properly using 'AppDomain.registerType()'.", classModule);
            var bridge: IADBridge = this.__typeBridges[typeInfo.$__fullname];
            if (!bridge) {
                var _this = this;
                bridge = <any>function ADBridge(): Object { return Delegate.fastApply(classModule, _this, arguments); };
                bridge.prototype = classModule; // (the bridge works only because "type" has STATIC properties, so a bridge object specific to this AppDomain is created, cached, and used)
                // ... create a shell type for creating instances of the actual type ...
                bridge.$__shellType = <any>(function () { this.constructor = classModule; (<Object.$Type>this).__appDomain = _this; });
                bridge.$__shellType.prototype = classModule.$Type.prototype;
                this.__typeBridges[typeInfo.$__fullname] = bridge;
            }
            return <any>bridge;
        }

        private __validateElementTarget(appTitle: string, targetElement: Platform.UI.IUIObject) { //??
            for (var i = 0; i < this.applications.length; i++)
                if ((<Platform.UI.View.$Type<any>><any>this.applications[i]).__uiElement == targetElement)
                    throw "Cannot add application '" + appTitle + "' as another application exists with the same target element.  Two applications cannot render to the same target.";
        }

        createApplication: <TApp extends Platform.Application.$Type>(appClassMod?: IClassModule<TApp>, parent?: Platform.GraphItem.$Type, title?: string, description?: string, targetElement?: HTMLElement) => TApp;
    }

    interface IADBridge { __appDomain: AppDomain; prototype: IClassModule<Object>; $__shellType: { new (); }; }

    (function () {
        AppDomain.prototype.createApplication = function createApplication<TApp extends Platform.Application.$Type>(appClassMod?: IClassModule<TApp>, parent?: Platform.GraphItem.$Type, title?: string, description?: string, targetElement?: HTMLElement): TApp {
            if (!Platform.Application)
                throw Exception.error("AppDomain.createApplication()", "");
            return (<AppDomain>this).with(<any>appClassMod || Platform.Application)(parent, title, description, targetElement);
        };

        if (Object.freeze)
            Object.freeze(AppDomain);
        // ... register the basic types ...
        AppDomain.registerType(global.Object, null, false);
        AppDomain.registerType(global.Function, null, false);
        AppDomain.registerType(global.Array, null, false);
        AppDomain.registerType(global.Boolean, null, false);
        AppDomain.registerType(global.Number, null, false);
        AppDomain.registerType(global.String, null, false);
        AppDomain.registerType(global.Date, null, false);
        AppDomain.registerType(global.RegExp, null, false);
    })();

    // =======================================================================================================================
} // (DreamSpace)

// ###########################################################################################################################
